Ištirkite pažangius TypeScript OOP šablonus. Šis vadovas apima klasės dizaino principus, paveldėjimo ir kompozicijos debatus bei praktines strategijas, skirtas kurti keičiamo dydžio, prižiūrimas programas globaliai auditorijai.
TypeScript OOP Šablonai: Klasės Dizaino ir Paveldėjimo Strategijų Vadovas
Šiuolaikinio programinės įrangos kūrimo pasaulyje TypeScript tapo kertiniu akmeniu kuriant patikimas, keičiamo dydžio ir prižiūrimas programas. Jo stipri tipų sistema, sukurta ant JavaScript, suteikia kūrėjams įrankius anksti pastebėti klaidas ir rašyti labiau nuspėjamą kodą. TypeScript galios pagrindas yra visapusiškas objektinio programavimo (OOP) principų palaikymas. Tačiau vien tik žinoti, kaip sukurti klasę, nepakanka. Norint įvaldyti TypeScript, reikia giliai suprasti klasės dizainą, paveldėjimo hierarchijas ir kompromisus tarp skirtingų architektūrinių šablonų.
Šis vadovas skirtas globaliai kūrėjų auditorijai, nuo tų, kurie įtvirtina savo tarpinius įgūdžius, iki patyrusių architektų. Mes giliai pasinersime į pagrindines OOP sąvokas TypeScript, išnagrinėsime efektyvias klasės dizaino strategijas ir spręsime amžius trunkantį ginčą: paveldėjimas prieš kompoziciją. Iki galo būsite aprūpinti žiniomis, kad galėtumėte priimti pagrįstus dizaino sprendimus, kurie lemia švaresnes, lankstesnes ir ateičiai atsparesnes kodo bazes.
OOP ramsčių supratimas TypeScript
Prieš pasinerdami į sudėtingus šablonus, sukurkime tvirtą pagrindą, pakartotinai peržiūrėdami keturis pagrindinius objektinio programavimo ramsčius, kaip jie taikomi TypeScript.
1. Inkapsuliacija
Inkapsuliacija yra principas, pagal kurį objekto duomenys (savybės) ir metodai, kurie veikia su tais duomenimis, yra sujungiami į vieną vienetą - klasę. Tai taip pat apima tiesioginio priėjimo prie objekto vidinės būsenos apribojimą. TypeScript tai pasiekia visų pirma per prieigos modifikatorius: public, private ir protected.
Pavyzdys: banko sąskaita, kurios likutis gali būti keičiamas tik per įmokų ir išėmimo metodus.
class BankAccount {
private balance: number = 0;
constructor(initialBalance: number) {
if (initialBalance >= 0) {
this.balance = initialBalance;
}
}
public deposit(amount: number): void {
if (amount > 0) {
this.balance += amount;
console.log(`Deposited: ${amount}. New balance: ${this.balance}`);
}
}
public getBalance(): number {
// We expose the balance through a method, not directly
return this.balance;
}
}
2. Abstrakcija
Abstrakcija reiškia sudėtingų įgyvendinimo detalių slėpimą ir tik esminių objekto savybių atskleidimą. Tai leidžia mums dirbti su aukšto lygio sąvokomis, nereikalaujant suprasti sudėtingų mechanizmų po jomis. TypeScript abstrakcija dažnai pasiekiama naudojant abstract klases ir interfaces.
Pavyzdys: kai naudojate nuotolinio valdymo pultą, tiesiog paspauskite mygtuką "Power". Jums nereikia žinoti apie infraraudonuosius signalus ar vidinę grandinę. Nuotolinio valdymo pultas suteikia abstrakčią sąsają su televizoriaus funkcionalumu.
3. Paveldėjimas
Paveldėjimas yra mechanizmas, kai nauja klasė (poklasis arba išvestinė klasė) paveldi savybes ir metodus iš esamos klasės (superklasės arba bazinės klasės). Tai skatina kodo pakartotinį naudojimą ir nustato aiškų "yra-a" ryšį tarp klasių. TypeScript naudoja raktinį žodį extends paveldėjimui.
Pavyzdys: `Manager` "yra-a" `Employee` tipo. Jie turi bendrų savybių, tokių kaip `name` ir `id`, bet `Manager` gali turėti papildomų savybių, tokių kaip `subordinates`.
class Employee {
constructor(public name: string, public id: number) {}
getProfile(): string {
return `Name: ${this.name}, ID: ${this.id}`;
}
}
class Manager extends Employee {
constructor(name: string, id: number, public subordinates: Employee[]) {
super(name, id); // Call the parent constructor
}
// Managers can also have their own methods
delegateTask(): void {
console.log(`${this.name} is delegating tasks.`);
}
}
4. Polimorfizmas
Polimorfizmas, kuris reiškia "daugelis formų", leidžia skirtingų klasių objektams būti traktuojamiems kaip bendros superklasės objektai. Tai leidžia vienai sąsajai (pvz., metodo pavadinimui) atspindėti skirtingas pagrindines formas (įgyvendinimus). Tai dažnai pasiekiama per metodo perrašymą.
Pavyzdys: `render()` metodas, kuris elgiasi skirtingai `Circle` objektui, palyginti su `Square` objektu, net jei abu yra `Shape`s.
abstract class Shape {
abstract draw(): void; // An abstract method must be implemented by subclasses
}
class Circle extends Shape {
draw(): void {
console.log("Drawing a circle.");
}
}
class Square extends Shape {
draw(): void {
console.log("Drawing a square.");
}
}
function renderShapes(shapes: Shape[]): void {
shapes.forEach(shape => shape.draw()); // Polymorphism in action!
}
const myShapes: Shape[] = [new Circle(), new Square()];
renderShapes(myShapes);
// Output:
// Drawing a circle.
// Drawing a square.
Didysis Debatas: Paveldėjimas prieš Kompoziciją
Tai yra vienas iš svarbiausių dizaino sprendimų OOP. Bendra išmintis šiuolaikinėje programinės įrangos inžinerijoje yra "teikti pirmenybę kompozicijai, o ne paveldėjimui." Supraskime, kodėl, nuodugniai išnagrinėdami abi sąvokas.
Kas yra Paveldėjimas? "Yra-a" Ryšys
Paveldėjimas sukuria stiprų ryšį tarp bazinės klasės ir išvestinės klasės. Kai naudojate `extends`, jūs teigiate, kad nauja klasė yra specializuota bazinės klasės versija. Tai galingas įrankis kodo pakartotiniam naudojimui, kai egzistuoja aiškus hierarchinis ryšys.
- Privalumai:
- Kodo pakartotinis naudojimas: Bendra logika apibrėžiama vieną kartą bazinėje klasėje.
- Polimorfizmas: Leidžia elegantišką, polimorfinį elgesį, kaip matėme mūsų `Shape` pavyzdyje.
- Aiški hierarchija: Ji modeliuoja realaus pasaulio, viršų į apačią klasifikavimo sistemą.
- Trūkumai:
- Stiprus ryšys: Bazinės klasės pakeitimai gali netyčia sugadinti išvestines klases. Tai žinoma kaip "trapios bazinės klasės problema".
- Hierarchijos pragaras: Pernelyg didelis naudojimas gali sukelti gilias, sudėtingas ir standžias paveldėjimo grandines, kurias sunku suprasti ir prižiūrėti.
- Nelankstus: Klasė gali paveldėti tik iš vienos kitos klasės TypeScript (vienkartinis paveldėjimas), o tai gali būti ribojantis. Jūs negalite paveldėti savybių iš kelių, nesusijusių klasių.
Kada Paveldėjimas yra Geras Pasirinkimas?
Naudokite paveldėjimą, kai ryšys yra tikrai "yra-a" ir yra stabilus bei mažai tikėtinas, kad pasikeis. Pavyzdžiui, `CheckingAccount` ir `SavingsAccount` iš esmės yra `BankAccount` tipai. Ši hierarchija yra prasminga ir mažai tikėtina, kad bus pertvarkyta.
Kas yra Kompozicija? "Turi-a" Ryšys
Kompozicija apima sudėtingų objektų konstravimą iš mažesnių, nepriklausomų objektų. Vietoj to, kad klasė būtų kažkas kita, ji turi kitus objektus, kurie suteikia reikiamą funkcionalumą. Tai sukuria laisvą ryšį, nes klasė sąveikauja tik su vieša sudėtinių objektų sąsaja.
- Privalumai:
- Lankstumas: Funkcionalumą galima pakeisti vykdymo metu, pakeičiant sudėtinius objektus.
- Laisvas ryšys: Konteinerinė klasė neturi žinoti vidinio naudojamų komponentų veikimo. Tai palengvina kodo testavimą ir priežiūrą.
- Vengiama hierarchijos problemų: Jūs galite sujungti funkcionalumus iš įvairių šaltinių, nesukuriant painaus paveldėjimo medžio.
- Aiški atsakomybė: Kiekviena komponentų klasė gali laikytis vieno atsakomybės principo.
- Trūkumai:
- Daugiau šabloninio kodo: Kartais gali prireikti daugiau kodo, kad būtų sujungti skirtingi komponentai, palyginti su paprastu paveldėjimo modeliu.
- Mažiau intuityvu hierarchijoms: Ji nemodeliavo natūralių taksonomijų taip tiesiogiai, kaip paveldėjimas.
Praktinis Pavyzdys: Automobilis
`Automobilis` yra puikus kompozicijos pavyzdys. `Automobilis` nėra `Variklis` tipas, nei jis yra `Ratas` tipas. Vietoj to, `Automobilis` turi `Variklį` ir turi `Ratus`.
// Component classes
class Engine {
start() {
console.log("Engine starting...");
}
}
class GPS {
navigate(destination: string) {
console.log(`Navigating to ${destination}...`);
}
}
// The composite class
class Car {
private readonly engine: Engine;
private readonly gps: GPS;
constructor() {
// The Car creates its own parts
this.engine = new Engine();
this.gps = new GPS();
}
driveTo(destination: string) {
this.engine.start();
this.gps.navigate(destination);
console.log("Car is on its way.");
}
}
const myCar = new Car();
myCar.driveTo("New York City");
Šis dizainas yra labai lankstus. Jei norime sukurti `Automobilį` su `ElektriniuVarikliu`, mums nereikia naujos paveldėjimo grandinės. Mes galime naudoti Priklausomybės įpurškimą, kad aprūpintume `Automobilį` jo komponentais, padarydami jį dar labiau modulinį.
interface IEngine {
start(): void;
}
class PetrolEngine implements IEngine {
start() { console.log("Petrol engine starting..."); }
}
class ElectricEngine implements IEngine {
start() { console.log("Silent electric engine starting..."); }
}
class AdvancedCar {
// The car depends on an abstraction (interface), not a concrete class
constructor(private engine: IEngine) {}
startJourney() {
this.engine.start();
console.log("Journey has begun.");
}
}
const tesla = new AdvancedCar(new ElectricEngine());
tesla.startJourney();
const ford = new AdvancedCar(new PetrolEngine());
ford.startJourney();
Pažangios Strategijos ir Šablonai TypeScript
Be pagrindinio pasirinkimo tarp paveldėjimo ir kompozicijos, TypeScript suteikia galingus įrankius kuriant sudėtingus ir lanksčius klasės dizainus.
1. Abstraktūs Klasės: Paveldėjimo Projektas
Kai turite stiprų "yra-a" ryšį, bet norite užtikrinti, kad bazinės klasės negali būti sukurtos atskirai, naudokite `abstract` klases. Jos veikia kaip projektas, apibrėžiantis bendrus metodus ir savybes, ir gali deklaruoti `abstract` metodus, kuriuos išvestinės klasės privalo įgyvendinti.
Naudojimo Atvejis: Mokėjimo apdorojimo sistema. Jūs žinote, kad kiekvienas šliuzas turi turėti `pay()` ir `refund()` metodus, bet įgyvendinimas yra specifinis kiekvienam teikėjui (pvz., Stripe, PayPal).
abstract class PaymentGateway {
constructor(public apiKey: string) {}
// A concrete method shared by all subclasses
protected connect(): void {
console.log("Connecting to payment service...");
}
// Abstract methods that subclasses must implement
abstract processPayment(amount: number): boolean;
abstract issueRefund(transactionId: string): boolean;
}
class StripeGateway extends PaymentGateway {
processPayment(amount: number): boolean {
this.connect();
console.log(`Processing ${amount} via Stripe.`);
return true;
}
issueRefund(transactionId: string): boolean {
console.log(`Refunding transaction ${transactionId} via Stripe.`);
return true;
}
}
// const gateway = new PaymentGateway("key"); // Error: Cannot create an instance of an abstract class.
const stripe = new StripeGateway("sk_test_123");
stripe.processPayment(100);
2. Sąsajos: Elgesio Sutarčių Apibrėžimas
Sąsajos TypeScript yra būdas apibrėžti klasės formos sutartį. Jos nurodo, kokias savybes ir metodus klasė turi turėti, bet jos neteikia jokio įgyvendinimo. Klasė gali `implement` kelias sąsajas, todėl jos yra kompozicinio ir atsieto dizaino kertinis akmuo.
Sąsaja vs. Abstrakti Klasė
- Naudokite abstraktią klasę, kai norite bendrai naudoti įgyvendintą kodą tarp kelių glaudžiai susijusių klasių.
- Naudokite sąsają, kai norite apibrėžti elgesio sutartį, kurią gali įgyvendinti skirtingos, nesusijusios klasės.
Naudojimo Atvejis: Sistemoje daug skirtingų objektų gali reikėti serializuoti į eilutės formatą (pvz., registravimui ar saugojimui). Šie objektai (`User`, `Product`, `Order`) yra nesusiję, bet turi bendrą galimybę.
interface ISerializable {
serialize(): string;
}
class User implements ISerializable {
constructor(public id: number, public name: string) {}
serialize(): string {
return JSON.stringify({ id: this.id, name: this.name });
}
}
class Product implements ISerializable {
constructor(public sku: string, public price: number) {}
serialize(): string {
return JSON.stringify({ sku: this.sku, price: this.price });
}
}
function logItems(items: ISerializable[]): void {
items.forEach(item => {
console.log("Serialized item:", item.serialize());
});
}
const user = new User(1, "Alice");
const product = new Product("TSHIRT-RED", 19.99);
logItems([user, product]);
3. Mišiniai: Kompozicinis Požiūris į Kodo Pakartotinį Naudojimą
Kadangi TypeScript leidžia tik vienkartinį paveldėjimą, ką daryti, jei norite pakartotinai naudoti kodą iš kelių šaltinių? Čia atsiranda mišinio šablonas. Mišiniai yra funkcijos, kurios paima konstruktorių ir grąžina naują konstruktorių, kuris jį praplečia nauju funkcionalumu. Tai yra kompozicijos forma, kuri leidžia jums "įmaišyti" galimybes į klasę.
Naudojimo Atvejis: Jūs norite pridėti `Timestamp` (su `createdAt`, `updatedAt`) ir `SoftDeletable` (su `deletedAt` savybe ir `softDelete()` metodu) elgesius prie kelių modelio klasių.
// A Type helper for mixins
type Constructor = new (...args: any[]) => T;
// Timestamp Mixin
function Timestamped(Base: TBase) {
return class extends Base {
createdAt: Date = new Date();
updatedAt: Date = new Date();
};
}
// SoftDeletable Mixin
function SoftDeletable(Base: TBase) {
return class extends Base {
deletedAt: Date | null = null;
softDelete() {
this.deletedAt = new Date();
console.log("Item has been soft deleted.");
}
};
}
// Base class
class DocumentModel {
constructor(public title: string) {}
}
// Create a new class by composing mixins
const UserAccountModel = SoftDeletable(Timestamped(DocumentModel));
const userAccount = new UserAccountModel("My User Account");
console.log(userAccount.title);
console.log(userAccount.createdAt);
userAccount.softDelete();
console.log(userAccount.deletedAt);
Išvada: Ateities Atsparių TypeScript Programų Kūrimas
Objektinio programavimo įvaldymas TypeScript yra kelionė nuo sintaksės supratimo iki dizaino filosofijos priėmimo. Sprendimai, kuriuos priimate dėl klasės struktūros, paveldėjimo ir kompozicijos, turi didelę įtaką ilgalaikei jūsų programos sveikatai.
Štai pagrindinės išvados jūsų globaliai kūrimo praktikai:
- Pradėkite nuo Ramsčių: Užtikrinkite, kad turite tvirtą inkapsuliacijos, abstrakcijos, paveldėjimo ir polimorfizmo supratimą. Jie yra OOP žodynas.
- Teikite pirmenybę kompozicijai, o ne paveldėjimui: Šis principas nuves jus prie lankstesnio, modulinio ir testuojamo kodo. Pradėkite nuo kompozicijos ir pasiekite paveldėjimą tik tada, kai egzistuoja aiškus, stabilus "yra-a" ryšys.
- Naudokite Tinkamą Įrankį Darbui:
- Naudokite Paveldėjimą tikrai specializacijai ir kodo dalijimuisi stabilioje hierarchijoje.
- Naudokite Abstraktas Klases, kad apibrėžtumėte bendrą pagrindą klasių šeimai, dalindamiesi kai kuriais įgyvendinimais, tuo pačiu užtikrindami sutartį.
- Naudokite Sąsajas, kad apibrėžtumėte elgesio sutartis, kurias gali įgyvendinti bet kuri klasė, skatinantį ekstremalų atskyrimą.
- Naudokite Mišinius, kai jums reikia sujungti funkcionalumus į klasę iš kelių šaltinių, įveikiant vienkartinio paveldėjimo apribojimus.
Kritiškai mąstydami apie šiuos šablonus ir suprasdami jų kompromisus, galite kurti TypeScript programas, kurios yra ne tik galingos ir efektyvios šiandien, bet ir lengvai pritaikomos, plečiamos ir prižiūrimos ateinančius metus - nesvarbu, kurioje pasaulio vietoje esate jūs ar jūsų komanda.